package me.youline.dataServer.service.account;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.transaction.Transactional;

import me.youline.dataServer.dao.UserBaseInfoDAO;
import me.youline.dataServer.dao.UserSocialConnectDAO;
import me.youline.dataServer.dao.UserSocialInfoRecorderDAO;
import me.youline.dataServer.dao.authentication.UserCredentialInfoDAO;
import me.youline.dataServer.entity.UserBaseInfo;
import me.youline.dataServer.entity.UserCredentialInfo;
import me.youline.dataServer.entity.UserRelationRecorder;
import me.youline.dataServer.entity.UserSocialConnect;
import me.youline.dataServer.entity.UserToken;
import me.youline.dataServer.exception.DuplicatedSocialAccountBindException;
import me.youline.dataServer.exception.ImageScaleException;
import me.youline.dataServer.exception.ReplicatedDataException;
import me.youline.dataServer.exception.UnAuthenticatedUserInfoException;
import me.youline.dataServer.exception.UnExistDataException;
import me.youline.dataServer.exception.UserAccoutExistException;
import me.youline.dataServer.service.OfficialUserService;
import me.youline.dataServer.service.SocialRelationService;
import me.youline.dataServer.service.UserDeviceTokenService;
import me.youline.dataServer.service.account.UserProfileOperationInfo.UserProfileAddInfo;
import me.youline.dataServer.service.account.UserProfileOperationInfo.UserProfileSocialAddInfo;
import me.youline.dataServer.service.authentication.UserCredentialInfoHelper;
import me.youline.dataServer.service.authentication.UserTokenService;
import me.youline.dataServer.service.image.ImageScaleService;
import me.youline.dataServer.service.image.RatioImageScaleContext;
import me.youline.dataServer.service.support.file.AvatarFileLocator.AvatarFileLocationConfig;
import me.youline.dataServer.service.support.file.FileBuilder.FileEntity;
import me.youline.dataServer.service.support.file.FileLocatorManager;

import org.apache.shiro.authc.UsernamePasswordToken;
import org.perf4j.aop.Profiled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

/**
 * 用户账户注册业务类，负责用户账户相关信息的创建、与第三方平台关联业务
 * 
 * @author linhan
 *
 */
@Service
public class UserAccountRegisterService {

	private static Logger logger = LoggerFactory
			.getLogger(UserAccountRegisterService.class);

	@Autowired
	private UserCredentialInfoHelper userCredentialInfoHelper;

	@Autowired
	private UserCredentialInfoDAO credentialDAO;

	@Autowired
	private UserTokenService userTokenService;

	@Autowired
	private UserBaseInfoDAO userBaseInfoDAO;

	@Autowired
	private FileLocatorManager fileLocatorManager;

	@Autowired
	private UserDeviceTokenService userDeviceTokenService;

	@Autowired
	private UserSocialInfoRecorderDAO relationTotalDAO;

	@Autowired
	private UserSocialConnectDAO connectDAO;

	@Autowired
	private ImageScaleService imageScaleService;

	@Autowired
	private OfficialUserService officialUserService;

	@Autowired
	private SocialRelationService socialRelationService;
	

	protected static final String DEFAULT_AVATAR_PATH = "/system/blank.jpg";

	/**
	 * 注册新用户 1.检验用户是否存在 2.为提交的用户信息绑定ID 3.设置用户信息中涉及文件资源的数据 4.持久化用户基本信息
	 * 5.设置用户身份认证信息 6.对第三方账号登陆后的注册信息进行处理 7.初始化与用户相关的其他信息，如用户社交关系统计数据等 8.发布token
	 * 9.存储文件资源
	 * 
	 * @param addInfo
	 * @return
	 * @throws UserAccoutExistException
	 */
	@Transactional
	@Profiled(tag = "service-注册账户")
	public UserToken registerUserProfile(UserProfileAddInfo addInfo)
			throws UserAccoutExistException {
		this.checkUserUnExist(addInfo.getUserName());
		UserBaseInfo waitForPersist = addInfo.getBaseInfo();
		this.bindUserId(waitForPersist);
		FileResourceProcessor fileProcessor = this.new FileResourceProcessor(
				addInfo, waitForPersist.getUserId());
		fileProcessor.setUserBaseInfoFileProps(waitForPersist);
		userBaseInfoDAO.save(waitForPersist);
		this.createUserCredential(waitForPersist.getUserId(),
				addInfo.getUserName(), addInfo.getPassword());
		this.decideIfExecuteSaveConnectInfo(waitForPersist.getUserId(), addInfo);
		this.initUserRelatedInfo(waitForPersist.getUserId());
		UserToken t = publishUserToken(waitForPersist.getUserId(),
				addInfo.getUserName());
		fileProcessor.processFileResource();
		return t;
	}

	/**
	 * 用户注册信息有关文件资源的处理器
	 * 
	 * @author linhan
	 *
	 */
	public class FileResourceProcessor {

		private List<FileEntity> fileEntityList = new ArrayList<FileEntity>();

		/**
		 * 是否存在文件资源信息
		 */
		private boolean isExistFileResource;

		public FileResourceProcessor(UserProfileAddInfo addInfo, Long userId) {
			if (addInfo.getAvatarFile() == null) {
				isExistFileResource = false;
			} else {
				fileEntityList.add(UserAccountRegisterService.this
						.getFileLocatorManager()
						.getAvatarFileLocator()
						.getNewFileEntity(
								new AvatarFileLocationConfig(addInfo
										.getAvatarFile(), userId)));
				isExistFileResource = true;
			}
		}

		/**
		 * 为用户基本信息设置文件资源相关数据
		 * 
		 * @param baseInfo
		 */
		public void setUserBaseInfoFileProps(UserBaseInfo baseInfo) {
			if (!isExistFileResource) {
				baseInfo.setAvatarUrl(DEFAULT_AVATAR_PATH);
				return;
			}
			baseInfo.setAvatarUrl(fileEntityList.get(0)
					.getFilePathExcludeRoot());
		}

		/**
		 * 执行针对不同的文件资源的处理业务。如头像会将原图保存，并进行压缩
		 */
		public void processFileResource() {
			if (!isExistFileResource) {
				return;
			}
			try {
				fileEntityList.get(0).save();
				imageScaleService.scaleImage(new RatioImageScaleContext(
						fileEntityList.get(0).getFile()));
			} catch (ImageScaleException | IOException e) {
				logger.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 检验用户是否已经存在，若是存在则会抛出用户账户存在异常。
	 * 
	 * @param userName
	 * @throws UserAccoutExistException
	 */
	protected void checkUserUnExist(String userName)
			throws UserAccoutExistException {
		if (credentialDAO.isExistUserName(userName)) {
			throw new UserAccoutExistException("当前用户名已经存在");
		}
	}

	/**
	 * 将用户个人信息绑定用户ID
	 * 
	 * @param baseInfo
	 */
	protected void bindUserId(UserBaseInfo baseInfo) {
		UserBaseInfo stubInfo = userBaseInfoDAO.save(baseInfo);
		baseInfo.setUserId(stubInfo.getUserId());
	}

	/**
	 * 发布token
	 * 
	 * @param userId
	 * @param userName
	 * @return
	 */
	protected UserToken publishUserToken(Long userId, String userName) {
		try {
			return userTokenService.publishAuthenticatedToken(userId, userName);
		} catch (UnAuthenticatedUserInfoException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 初始化与用户个人相关的其他信息
	 * 
	 * @param userId
	 */
	protected void initUserRelatedInfo(Long userId) {
		relationTotalDAO.addUserRelationTotal(new UserRelationRecorder(userId));// 创建用户关系统计实体
		userDeviceTokenService.addUserDeviceToken(userId, null);
		try {
			this.followingOfficialUsers(userId);
		} catch (Exception e) {
			//异常丢弃，此处允许关注默认官方号失败
		}
	}

	protected void followingOfficialUsers(Long userId)
			throws ReplicatedDataException, UnExistDataException {
		for (Long id : officialUserService.getFollowedOfficialUsers()) {
			socialRelationService.buildSocialRelation(userId, id);
		}
	}

	/**
	 * 创建用户身份凭证
	 * 
	 * @param userId
	 * @param userName
	 * @param password
	 */
	protected void createUserCredential(Long userId, String userName,
			String password) {
		UserCredentialInfo info = userCredentialInfoHelper
				.getCredentialInfoByToken(new UsernamePasswordToken(userName,
						password));
		info.setUserId(userId);
		credentialDAO.save(info);
	}

	/**
	 * 获取用户第三方关联信息
	 * 
	 * @param userId
	 * @param addInfo
	 * @return
	 */
	protected UserSocialConnect getUserSocialConnect(Long userId,
			UserProfileSocialAddInfo addInfo) {
		UserSocialConnect connect = new UserSocialConnect();
		connect.setConnectType(addInfo.getConnectType());
		connect.setSocialConnectId(addInfo.getSocialConnectId());
		connect.setUserId(userId);
		return connect;
	}

	/**
	 * 决定是否需要建立第三方账户与用户信息关联
	 * 
	 * @param userId
	 * @param info
	 */
	protected void decideIfExecuteSaveConnectInfo(Long userId,
			UserProfileAddInfo info) {
		if (info instanceof UserProfileSocialAddInfo) {
			try {
				connectDAO.addSocialConnect(getUserSocialConnect(userId,
						(UserProfileSocialAddInfo) info));
			} catch (DuplicateKeyException e) {
				throw new DuplicatedSocialAccountBindException("社交账户已经绑定");
			}
		} else {
			return;
		}
	}

	public UserCredentialInfoHelper getUserCredentialInfoHelper() {
		return userCredentialInfoHelper;
	}

	public void setUserCredentialInfoHelper(
			UserCredentialInfoHelper userCredentialInfoHelper) {
		this.userCredentialInfoHelper = userCredentialInfoHelper;
	}

	public UserCredentialInfoDAO getCredentialDAO() {
		return credentialDAO;
	}

	public void setCredentialDAO(UserCredentialInfoDAO credentialDAO) {
		this.credentialDAO = credentialDAO;
	}

	public UserTokenService getUserTokenService() {
		return userTokenService;
	}

	public void setUserTokenService(UserTokenService userTokenService) {
		this.userTokenService = userTokenService;
	}

	public UserBaseInfoDAO getUserBaseInfoDAO() {
		return userBaseInfoDAO;
	}

	public void setUserBaseInfoDAO(UserBaseInfoDAO userBaseInfoDAO) {
		this.userBaseInfoDAO = userBaseInfoDAO;
	}

	public FileLocatorManager getFileLocatorManager() {
		return fileLocatorManager;
	}

	public void setFileLocatorManager(FileLocatorManager fileLocatorManager) {
		this.fileLocatorManager = fileLocatorManager;
	}

	public UserDeviceTokenService getUserDeviceTokenService() {
		return userDeviceTokenService;
	}

	public void setUserDeviceTokenService(
			UserDeviceTokenService userDeviceTokenService) {
		this.userDeviceTokenService = userDeviceTokenService;
	}

	public UserSocialInfoRecorderDAO getRelationTotalDAO() {
		return relationTotalDAO;
	}

	public void setRelationTotalDAO(UserSocialInfoRecorderDAO relationTotalDAO) {
		this.relationTotalDAO = relationTotalDAO;
	}

	public UserSocialConnectDAO getConnectDAO() {
		return connectDAO;
	}

	public void setConnectDAO(UserSocialConnectDAO connectDAO) {
		this.connectDAO = connectDAO;
	}

	public ImageScaleService getImageScaleService() {
		return imageScaleService;
	}

	public void setImageScaleService(ImageScaleService imageScaleService) {
		this.imageScaleService = imageScaleService;
	}

	public OfficialUserService getOfficialUserService() {
		return officialUserService;
	}

	public void setOfficialUserService(OfficialUserService officialUserService) {
		this.officialUserService = officialUserService;
	}

	public SocialRelationService getSocialRelationService() {
		return socialRelationService;
	}

	public void setSocialRelationService(SocialRelationService socialRelationService) {
		this.socialRelationService = socialRelationService;
	}
	
}
